{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5rmpybwysXGV"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "m8y3rGtQsYP2"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hrXv0rU9sIma"
      },
      "source": [
        "# 基本训练循环"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7S0BwJ_8sLu7"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>     <a target=\"_blank\" href=\"https://tensorflow.google.cn/guide/basic_training_loops\"><img src=\"https://tensorflow.google.cn/images/tf_logo_32px.png\">在 TensorFlow.org 上查看</a>\n",
        "</td>\n",
        "  <td>     <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/zh-cn/guide/basic_training_loops.ipynb\"><img src=\"https://tensorflow.google.cn/images/colab_logo_32px.png\">在 Google Colab 中运行</a>\n",
        "</td>\n",
        "  <td>     <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/zh-cn/guide/basic_training_loops.ipynb\"><img src=\"https://tensorflow.google.cn/images/GitHub-Mark-32px.png\">在 GitHub 上查看源代码</a>\n",
        "</td>\n",
        "  <td>     <a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/zh-cn/guide/basic_training_loops.ipynb\"><img src=\"https://tensorflow.google.cn/images/download_logo_32px.png\">下载笔记本</a>\n",
        "</td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "k2o3TTG4TFpt"
      },
      "source": [
        "在前面的教程里，您已经了解了[tensors](./tensor.ipynb), [variables](./variable.ipynb), [gradient tape](autodiff.ipynb), 和[modules](./intro_to_modules.ipynb)。在这篇教程，您将把它们放在一起训练模型。\n",
        "\n",
        "TensorFlow 同样具有更高层次的神经网络应用程序编程接口 [tf.Keras](keras/overview.ipynb)。它提供有效的抽象来减少代码量。然而，这篇教程中，您将使用基础类。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3LXMVuV0VhDr"
      },
      "source": [
        "## 创建"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NiolgWMPgpwI"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iKD__8kFCKNt"
      },
      "source": [
        "## 解决机器学习问题\n",
        "\n",
        "解决一个机器学习问题通常包含以下步骤：\n",
        "\n",
        "- 获得训练数据。\n",
        "- 定义模型。\n",
        "- 定义损失函数。\n",
        "- 遍历训练数据，从目标值计算损失。\n",
        "- 计算该损失的梯度，并使用*optimizer*调整变量以适合数据。\n",
        "- 计算结果。\n",
        "\n",
        "为了便于说明，在本指南中，您将开发一个简单的线性模型, $f(x) = x * W + b$, 其中包含两个变量: $W$ (权重) 和 $b$ (偏差)。\n",
        "\n",
        "这是一个最基础的机器学习问题: 已知 $x$ 和 $y$, 尝试解得直线的斜率和偏移量。 [simple linear regression](https://en.wikipedia.org/wiki/Linear_regression#Simple_and_multiple_linear_regress"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qutT_fkl_CBc"
      },
      "source": [
        "## 数据\n",
        "\n",
        "监督学习使用*输入*（通常表示为 *x*）和*输出*（表示为 *y*，通常称为*标签*）。目标是从成对的输入和输出中学习，以便您可以根据输入预测输出的值。\n",
        "\n",
        "TensorFlow中几乎每个输入数据都是由张量表示，并且通常是向量。监督学习中，输出(即想到预测值)同样是个张量。\n",
        "\n",
        "这是通过将高斯（即正态分布）噪声添加到直线上的点而合成的一些数据。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NzivK2ATByOz"
      },
      "outputs": [],
      "source": [
        "# 实际的线\n",
        "TRUE_W = 3.0\n",
        "TRUE_B = 2.0\n",
        "\n",
        "NUM_EXAMPLES = 1000\n",
        "\n",
        "# 随机向量x\n",
        "x = tf.random.normal(shape=[NUM_EXAMPLES])\n",
        "\n",
        "# 生成噪声\n",
        "noise = tf.random.normal(shape=[NUM_EXAMPLES])\n",
        "\n",
        "# 计算y\n",
        "y = x * TRUE_W + TRUE_B + noise"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IlFd_HVBFGIF"
      },
      "outputs": [],
      "source": [
        "# 绘制所有的数据\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "plt.scatter(x, y, c=\"b\")\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UH95XUzhL99d"
      },
      "source": [
        "张量通常以 *batches* 的形式聚集在一起，或者是成组的输入和输出堆叠在一起。批处理能够对训练过程带来一些好处，并且可以与加速器和矢量化计算很好地配合使用。给定此数据集的大小，您可以将整个数据集视为一个批次。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gFzH64Jn9PIm"
      },
      "source": [
        "## 定义模型\n",
        "\n",
        "使用 `tf.Variable` 代表模型中的所有权重。`tf.Variable` 能够存储值，并根据需要以张量形式提供它。详情请见 [variable guide](./variable.ipynb)。\n",
        "\n",
        "使用 `tf.Module` 封装变量和计算。您可以使用任何Python对象，但是通过这种方式可以轻松保存它。\n",
        "\n",
        "这里，您可以定义 *w* 和 *b* 为变量。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "_WRu7Pze7wk8"
      },
      "outputs": [],
      "source": [
        "class MyModel(tf.Module):\n",
        "  def __init__(self, **kwargs):\n",
        "    super().__init__(**kwargs)\n",
        "    # 初始化权重值为`5.0`，偏差值为`0.0`\n",
        "    # 实际项目中，应该随机初始化\n",
        "    self.w = tf.Variable(5.0)\n",
        "    self.b = tf.Variable(0.0)\n",
        "\n",
        "  def __call__(self, x):\n",
        "    return self.w * x + self.b\n",
        "\n",
        "model = MyModel()\n",
        "\n",
        "# 列出变量tf.modules的内置变量聚合\n",
        "print(\"Variables:\", model.variables)\n",
        "\n",
        "# 验证模型是否有效\n",
        "assert model(3.0).numpy() == 15.0"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rdpN_3ssG9D5"
      },
      "source": [
        "初始化变量在这里是以固定方式设置的，但是Keras附带了许多您可以使用的 [initalizers](https://tensorflow.google.cn/api_docs/python/tf/keras/initializers) 。无论是否使用其余的Keras模块，都不影响你对初始化器的使用。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xa6j_yXa-j79"
      },
      "source": [
        "### 定义损失函数\n",
        "\n",
        "损失函数衡量给定输入的模型输出与目标输出的匹配程度。目的是在训练过程中尽量减少这种差异。定义标准的L2损失，也称为“均方误差”:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Y0ysUFGY924U"
      },
      "outputs": [],
      "source": [
        "# 计算整个批次的单个损失值\n",
        "def loss(target_y, predicted_y):\n",
        "  return tf.reduce_mean(tf.square(target_y - predicted_y))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-50nq-wPBsAW"
      },
      "source": [
        "在训练模型之前，您可以可视化损失值。使用红色绘制模型的预测值，使用蓝色绘制训练数据。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "_eb83LtrB4nt"
      },
      "outputs": [],
      "source": [
        "plt.scatter(x, y, c=\"b\")\n",
        "plt.scatter(x, model(x), c=\"r\")\n",
        "plt.show()\n",
        "\n",
        "print(\"Current loss: %1.6f\" % loss(model(x), y).numpy())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sSDP-yeq_4jE"
      },
      "source": [
        "### 定义训练循环\n",
        "\n",
        "训练循环按顺序重复执行以下任务：\n",
        "\n",
        "- 发送一批输入值，通过模型生成输出值\n",
        "- 通过比较输出值与输出（标签），来计算损失值\n",
        "- 使用梯度带(GradientTape)找到梯度值\n",
        "- 使用这些梯度优化变量\n",
        "\n",
        "这个例子中，您可以使用 [gradient descent](https://en.wikipedia.org/wiki/Gradient_descent)训练数据。\n",
        "\n",
        "`tf.keras.optimizers`中有许多梯度下降的变量。但是本着搭建的第一原则，您将在这里 借助`tf.GradientTape`的自动微分和`tf.assign_sub`的递减值（结合了`tf.assign`和`tf.sub`）自己实现基本数学："
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "MBIACgdnA55X"
      },
      "outputs": [],
      "source": [
        "# 给定一个可调用的模型，输入，输出和学习率...\n",
        "def train(model, x, y, learning_rate):\n",
        "\n",
        "  with tf.GradientTape() as t:\n",
        "    # 可训练变量由GradientTape自动跟踪\n",
        "    current_loss = loss(y, model(x))\n",
        "\n",
        "  # 使用GradientTape计算相对于W和b的梯度\n",
        "  dw, db = t.gradient(current_loss, [model.w, model.b])\n",
        "\n",
        "  # 减去由学习率缩放的梯度\n",
        "  model.w.assign_sub(learning_rate * dw)\n",
        "  model.b.assign_sub(learning_rate * db)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RwWPaJryD2aN"
      },
      "source": [
        "为了了解训练，您可以发送同一批* x *和* y * 经过循环训练，同时查看`W`和`b`的变化情况。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "XdfkR223D9dW"
      },
      "outputs": [],
      "source": [
        "model = MyModel()\n",
        "\n",
        "# 收集W值和b值的历史记录以供以后绘制\n",
        "Ws, bs = [], []\n",
        "epochs = range(10)\n",
        "\n",
        "# 定义用于训练的循环\n",
        "def training_loop(model, x, y):\n",
        "\n",
        "  for epoch in epochs:\n",
        "    # 用单个大批次处理更新模型\n",
        "    train(model, x, y, learning_rate=0.1)\n",
        "\n",
        "    # 在更新之前进行跟踪\n",
        "    Ws.append(model.w.numpy())\n",
        "    bs.append(model.b.numpy())\n",
        "    current_loss = loss(y, model(x))\n",
        "\n",
        "    print(\"Epoch %2d: W=%1.2f b=%1.2f, loss=%2.5f\" %\n",
        "          (epoch, Ws[-1], bs[-1], current_loss))\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "iRuNUghs1lHY"
      },
      "outputs": [],
      "source": [
        "print(\"Starting: W=%1.2f b=%1.2f, loss=%2.5f\" %\n",
        "      (model.w, model.b, loss(y, model(x))))\n",
        "\n",
        "# 开始训练\n",
        "training_loop(model, x, y)\n",
        "\n",
        "# 绘制\n",
        "plt.plot(epochs, Ws, \"r\",\n",
        "         epochs, bs, \"b\")\n",
        "\n",
        "plt.plot([TRUE_W] * len(epochs), \"r--\",\n",
        "         [TRUE_B] * len(epochs), \"b--\")\n",
        "\n",
        "plt.legend([\"W\", \"b\", \"True W\", \"True b\"])\n",
        "plt.show()\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "tpTEjWWex568"
      },
      "outputs": [],
      "source": [
        "# 可视化训练后的模型如何执行\n",
        "plt.scatter(x, y, c=\"b\")\n",
        "plt.scatter(x, model(x), c=\"r\")\n",
        "plt.show()\n",
        "\n",
        "print(\"Current loss: %1.6f\" % loss(model(x), y).numpy())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DODMMmfLIiOC"
      },
      "source": [
        "## 使用Keras完成相同的解决方案\n",
        "\n",
        "将上面的代码与Keras中的等效代码进行对比很有用。\n",
        "\n",
        "如果您将`tf.keras.Model`子类化，则定义模型与其看起来完全相同。请记住，Keras模型最终从模块继承。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Z86hCI0x1YX3"
      },
      "outputs": [],
      "source": [
        "class MyModelKeras(tf.keras.Model):\n",
        "  def __init__(self, **kwargs):\n",
        "    super().__init__(**kwargs)\n",
        "    # 初始化权重为`5.0`，偏差为`0.0`\n",
        "    # 实际中应该随机初始化该值\n",
        "    self.w = tf.Variable(5.0)\n",
        "    self.b = tf.Variable(0.0)\n",
        "\n",
        "  def __call__(self, x, **kwargs):\n",
        "    return self.w * x + self.b\n",
        "\n",
        "keras_model = MyModelKeras()\n",
        "\n",
        "# 使用Keras模型重新进行循环训练\n",
        "training_loop(keras_model, x, y)\n",
        "\n",
        "# 您同样可以使用Keras内置的功能保存检查点（checkpoint）\n",
        "keras_model.save_weights(\"my_checkpoint\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6kw5P4jt2Az8"
      },
      "source": [
        "您可以使用Keras的内置功能作为捷径，而不必在每次创建模型时都编写新的训练循环。当您不想编写或调试Python训练循环时，这很有用。\n",
        "\n",
        "如果您使用Keras，您将会需要使用 `model.compile()` 去设置参数, 使用`model.fit()` 进行训练。借助Keras实现L2损失和梯度下降需要的代码量更少，就像一个捷径。Keras损失和优化器也可以在这些便利功能之外使用，而前面的示例也可以使用它们。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-nbLLfPE2pEl"
      },
      "outputs": [],
      "source": [
        "keras_model = MyModelKeras()\n",
        "\n",
        "# 编译设置培训参数\n",
        "keras_model.compile(\n",
        "    # 默认情况下，fit()调用tf.function()。\n",
        "    # Debug时你可以关闭这一功能，但是现在是打开的。\n",
        "    run_eagerly=False,\n",
        "\n",
        "    # 使用内置的优化器，配置为对象\n",
        "    optimizer=tf.keras.optimizers.SGD(learning_rate=0.1),\n",
        "\n",
        "    # Keras内置MSE\n",
        "    # 您也可以使用损失函数像上面一样进行定义\n",
        "    loss=tf.keras.losses.mean_squared_error,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lrlHODiZccu2"
      },
      "source": [
        "Keras`fit`期望批处理数据或完整的数据集作为NumPy数组。 NumPy数组分为多个批次，默认批次大小为32。\n",
        "\n",
        "这一案例中，为了匹配手写训练循环，您应该以大小为1000的单批次传递x。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zfAYqtu136PO"
      },
      "outputs": [],
      "source": [
        "print(x.shape[0])\n",
        "keras_model.fit(x, y, epochs=10, batch_size=1000)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8zKZIO9P5s1G"
      },
      "source": [
        "请注意，Keras会在训练后而不是之前打印出损失，因此第一次损失会显得较低。否则，这表明本质上相同的训练效果。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vPnIVuaSJwWz"
      },
      "source": [
        "## 下一步\n",
        "\n",
        "在这篇引导中，您已经看到怎样使用tensors, variables, modules, 和gradient tape去构建并训练模型，也进一步了解到这些是如何与Keras对应的。\n",
        "\n",
        "但是，这是一个极其简单的问题。有关更实用的介绍，请参阅[自定义训练演示](../tutorials/customization/custom_training_walkthrough.ipynb)。\n",
        "\n",
        "有关使用内置Keras训练循环的更多信息，参见[this guide](keras/train_and_evaluate.ipynb)。有关训练循环和Keras的更多信息，参见 [this guide](keras/writing_a_training_loop_from_scratch.ipynb)。有关自定义的分布式训练循环，参见 [this guide](./guide/distributed_training.ipynb#using_tfdistributestrategy_with_basic_training_loops_loops)。"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [
        "5rmpybwysXGV",
        "iKD__8kFCKNt",
        "vPnIVuaSJwWz"
      ],
      "name": "basic_training_loops.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
